﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Eswat.IronPython;
using Eswat.IronRuby;
using Eswat.PowerShell;
using Eswat.Razor;
using Microsoft.Practices.ServiceLocation;
using RazorEngine.Templating;

namespace Eswat.Client.Wpf.ViewModels
{
	/// <summary>
	/// View-model for a test runner.
	/// </summary>
	public class TestRunnerViewModel : ViewModelBase
	{
		private readonly ITestResultLoggingService _testResultLoggingService;

		/// <summary>
		/// Initializes a new instance of the <see cref="TestRunnerViewModel"/> class.
		/// </summary>
		public TestRunnerViewModel()
			: base()
		{
			this._testResultLoggingService = ServiceLocator.Current.GetInstance<ITestResultLoggingService>();

			this.Init();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="TestRunnerViewModel"/> class.
		/// </summary>
		/// <param name="testResultLoggingService">The test result logging service.</param>
		public TestRunnerViewModel(
			ITestResultLoggingService testResultLoggingService)
			: base()
		{
			this._testResultLoggingService = testResultLoggingService;

			this.Init();
		}

		/// <summary>
		/// Inits this instance.
		/// </summary>
		private void Init()
		{
			this.TestRuntimes = new ObservableCollection<string>(new string[] { "Razor", "PowerShell", "IronPython", "IronRuby" });
			this.RunTestCommand = new DelegateCommand(param => true, this.ExecuteTest);

			this.SelectedTestRuntime = "Razor";

			this._testResultLoggingService.Results.CollectionChanged += this.TestResults_CollectionChanged;
		}

		private List<TestResultViewModel> _testResults;

		/// <summary>
		/// Gets the test results.
		/// </summary>
		public IEnumerable<TestResultViewModel> TestResults
		{
			get { return this._testResults; }

			private set
			{
				this._testResults = value.ToList();
				this.OnPropertyChanged(() => this.TestResults);
			}
		}

		private Cursor _cursor;

		/// <summary>
		/// Gets the cursor.
		/// </summary>
		public Cursor Cursor
		{
			get { return this._cursor; }

			private set
			{
				this._cursor = value;
				this.OnPropertyChanged(() => this.Cursor);
			}
		}

		/// <summary>
		/// Handles the CollectionChanged event of the Results control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		private void TestResults_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				foreach (TestResult newTestResult in e.NewItems)
				{
					var viewModel = new TestResultViewModel(newTestResult);

					this._testResults.Add(viewModel);

					this.TestOutput += FormatWith("{0}{1}", Environment.NewLine, newTestResult);
				}

				this.OnPropertyChanged(() => this.TestOutput);
			}
			else
			{
				this._testResults.Clear();
				this.TestOutput = string.Empty;
			}
		}

		/// <summary>
		/// Gets the test runtimes.
		/// </summary>
		public ObservableCollection<string> TestRuntimes { get; private set; }

		/// <summary>
		/// Gets the run test command.
		/// </summary>
		public ICommand RunTestCommand { get; private set; }

		private string _selectedTestRuntime;

		/// <summary>
		/// Gets or sets the selected test runtime.
		/// </summary>
		/// <value>
		/// The selected test runtime.
		/// </value>
		public string SelectedTestRuntime
		{
			get { return this._selectedTestRuntime; }

			set
			{
				if (value != this._selectedTestRuntime)
				{
					this._selectedTestRuntime = value;
					this.OnPropertyChanged(() => this.SelectedTestRuntime);
				}
			}
		}

		private string _testScriptContent;

		/// <summary>
		/// Gets or sets the content of the test script.
		/// </summary>
		/// <value>
		/// The content of the test script.
		/// </value>
		public string TestScriptContent
		{
			get { return this._testScriptContent; }

			set
			{
				if (value != this._testScriptContent)
				{
					this._testScriptContent = value;
					this.OnPropertyChanged(() => this.TestScriptContent);
				}
			}
		}

		private string _testOutput;

		/// <summary>
		/// Gets the test output.
		/// </summary>
		public string TestOutput
		{
			get { return this._testOutput; }

			private set
			{
				if (value != this._testOutput)
				{
					this._testOutput = value;
					this.OnPropertyChanged(() => this.TestOutput);
				}
			}
		}

		/// <summary>
		/// Executes the test.
		/// </summary>
		/// <param name="parameter">The parameter.</param>
		private void ExecuteTest(object parameter)
		{
			this.Cursor = Cursors.Wait;

			this.TestResults = new TestResultViewModel[0];

			this._testResultLoggingService.Results.Clear();
			this.TestOutput = string.Empty;

			var started = DateTime.Now;

			try
			{
				var runtime = this.SelectedTestRuntime.ToLowerInvariant();

				if (runtime == "razor") this.ExecuteRazorTest(parameter);
				else if (runtime == "powershell") this.ExecutePowerShellTest(parameter);
				else if (runtime == "ironpython") this.ExecuteIronPythonTest(parameter);
				else if (runtime == "ironruby") this.ExecuteIronRubyTest(parameter);
				else throw new InvalidOperationException(FormatWith("Invalid runtime selection: {0}", runtime));
			}
			catch (Exception exception)
			{
				this.TestOutput += FormatWith("{0}Error ({1}): {2}{0}{3}{0}", Environment.NewLine, DateTime.Now, exception.Message, exception.ToString());
			}

			var ss = DateTime.Now.Subtract(started).TotalSeconds;

			this.TestOutput += FormatWith("{0}{3} {1} tests ran, taking {4} seconds", Environment.NewLine, this.SelectedTestRuntime, DateTime.Now, this._testResults.Count, ss);

			this.TestResults = this._testResults;

			this.Cursor = Cursors.Arrow;
		}

		/// <summary>
		/// Executes the power shell test.
		/// </summary>
		/// <param name="parameter">The parameter.</param>
		private void ExecutePowerShellTest(object parameter)
		{
			var testRunner = new PowerShellTestRunner();
			var testContext = new TestContext<string>("hello world", "PowerShell Custom Script", this.TestScriptContent);

			var results = testRunner.Execute(testContext);

			if (results.IsError) throw results.InnerException;
			else
			{
				foreach (var result in results.Result)
				{
					this.TestOutput += FormatWith("{0}{1}{0}", Environment.NewLine, result == null ? string.Empty : result.ToString());
				}
			}

			this.OnPropertyChanged(() => this.TestOutput);
		}

		/// <summary>
		/// Executes the iron python test.
		/// </summary>
		/// <param name="parameter">The parameter.</param>
		private void ExecuteIronPythonTest(object parameter)
		{
			var testRunner = new IronPythonTestRunner();
			var testContext = new TestContext<string>("hello world", "IronPython Custom Script", this.TestScriptContent);

			var results = testRunner.Execute(testContext);

			if (results.IsError) throw results.InnerException;
			else
			{
				this.TestOutput += FormatWith("{0}{1}", Environment.NewLine, results.Result ?? string.Empty);
			}

			this.OnPropertyChanged(() => this.TestOutput);
		}

		/// <summary>
		/// Executes the iron ruby test.
		/// </summary>
		/// <param name="parameter">The parameter.</param>
		private void ExecuteIronRubyTest(object parameter)
		{
			var testRunner = new IronRubyTestRunner();
			var testContext = new TestContext<string>("hello world", "IronRuby Custom Script", this.TestScriptContent);

			var results = testRunner.Execute(testContext);

			if (results.IsError) throw results.InnerException;
			else
			{
				this.TestOutput += FormatWith("{0}{1}", Environment.NewLine, results.Result ?? string.Empty);
			}

			this.OnPropertyChanged(() => this.TestOutput);
		}

		/// <summary>
		/// Executes the razor test.
		/// </summary>
		/// <param name="parameter">The parameter.</param>
		private void ExecuteRazorTest(object parameter)
		{
			try
			{
				var testRunner = new RazorTestRunner();
				var testContext = new TestContext<string>("hello world", "Razor Custom Script", this.TestScriptContent);

				var results = testRunner.Execute(testContext);
				var output = new StringBuilder();

				if (results.IsError) throw results.InnerException;
				else
				{
					this.TestOutput += FormatWith("{0}{1}", Environment.NewLine, results.Result);
				}
			}
			catch (TemplateCompilationException templateCompilationException)
			{
				this.TestOutput += FormatWith("{0}Error ({1}): {2}{0}", Environment.NewLine, DateTime.Now, templateCompilationException.Message);

				foreach (var error in templateCompilationException.Errors)
				{
					this.TestOutput += FormatWith("Compile Error {1} ({2}, {3}):{0}{4}{0}", Environment.NewLine, error.ErrorNumber, error.Line, error.Column, error.ErrorText);
				}

				this.TestOutput += FormatWith("{1}{0}", Environment.NewLine, templateCompilationException.ToString());
			}

			this.OnPropertyChanged(() => this.TestOutput);
		}
	}
}
